
#include <my_header.h>

// 设置文件类型和权限字符串
void set_type_name(mode_t mode, char *tm_str);
// 获取格式化后的字符串
void set_time(time_t mtime, char *time_str);
// qsort比较函数
int compare(const void *a, const void *b);
// 字符串转小写
void str_to_lower(char *str);


/* Usage: ./8_qsort_re_myls_al dirname */
int main(int argc, char *argv[]){                                  
    char *path; // 存储路径
    if(argc == 1)
    {
        path = "."; // 当前目录
    }else if(argc == 2)
    {
        path = argv[1];
    }else{
        fprintf(stderr, "argus nums error!\n");
        return 1;
    }

    // path是一个目录的路径名
    DIR *dirp = opendir(path);
    ERROR_CHECK(dirp, NULL, "opendir");

    // 切换工作目录为待打印的目录
    int ret = chdir(path);
    ERROR_CHECK(ret, -1, "chdir");

    // 遍历目录项构建dirent结构体数组，用于实现排序
    // 为了更好的排序性能，选择使用结构体指针数组，而不是结构体数组
    // 为了程序能够更加灵活使用，选择动态内存分配这个结构体指针数组
    // 先遍历一次计算目录项的个数
    int count = 0;
    struct dirent *dp;

    // 第一次遍历：计算目录项数量
    while((dp = readdir(dirp)) != NULL)
    {
        count++;
    }// while循环结束时,dp是一个空指针,count就是目录项的个数

    struct dirent **dir_arr = (struct dirent **)malloc
        (sizeof(struct dirent*) * count);
    ERROR_CHECK(dir_arr, NULL, "malloc dlist");

    // 倒带目录流指针
    rewinddir(dirp);

    // 第二次遍历：将目录项结构体对象指针存入数组
    int idx = 0;
    while((dp = readdir(dirp)) != NULL)
    {
        dir_arr[idx] = dp;
        idx++;
    }// while循环结束时，待打印目录下的所有文件的d
     // irent结构体指针斗被存入了dir_arr数组

     // 按照名字的字典排序，利用qsort函数排序dir_arr数组
    qsort(dir_arr, count, sizeof(struct dirent *), compare);

    // closedir(dirp);

    for(int i=0; i<count; i++)
    {
        struct stat sb;
        // 文件名不在从dirent结构体中获取
        int ret = stat(dir_arr[i]->d_name, &sb);
        ERROR_CHECK(ret, -1, "stat");

        // 1.处理stat的成员st_mode, 将它转换成权限和类型字符串
        char tm_str[1024] = { 0 };
        set_type_name(sb.st_mode, tm_str);

        // 2.获取用户名和组名
        char *username = getpwuid(sb.st_uid)->pw_name;
        char *gname = getgrgid(sb.st_gid)->gr_name;

        // 3.将时间戳转换成时间字符串
        char time_str[1024] = {0};
        set_time(sb.st_mtim.tv_sec, time_str);

        printf("%s %2lu %s %s %6lu %s %s\n",
               tm_str,
               sb.st_nlink,
               username,
               gname,
               sb.st_size,
               time_str, 
               // 从数组中获取文件名
               dir_arr[i]->d_name);
    }
    // 不要忘记free动态申请的目录项结构体指针数组
    free(dir_arr);

    // readdir返回的目录流结构体内存是由目录流自身管理的，会随着关闭目录流而自动释放
    // 所以如果在上面关闭目录流，极有可能导致目录项结构体被释放，从而导致数据漏输出

    // 关闭目录文件流
    closedir(dirp);
    return 0;
}

void set_type_name(mode_t mode, char *tm_str){
    // 处理第一个字符，即文件类型 
    switch (mode & S_IFMT) {
    case S_IFBLK:   tm_str[0] = 'b';        break;
    case S_IFCHR:   tm_str[0] = 'c';        break;
    case S_IFDIR:   tm_str[0] = 'd';        break;
    case S_IFIFO:   tm_str[0] = 'p';        break;
    case S_IFLNK:   tm_str[0] = 'l';        break;
    case S_IFREG:   tm_str[0] = '-';        break;
    case S_IFSOCK:  tm_str[0] = 's';        break;
    default:        tm_str[0] = '?';        break;
    }
    // 处理后续九个字符，即文件的权限信息
    // 设置拥有者的权限信息
    tm_str[1] = (mode & 0400) ? 'r' : '-';
    tm_str[2] = (mode & 0200) ? 'w' : '-';
    tm_str[3] = (mode & 0100) ? 'x' : '-';
    // 设置拥有者组的权限信息
    tm_str[4] = (mode & 0040) ? 'r' : '-';
    tm_str[5] = (mode & 0020) ? 'w' : '-';
    tm_str[6] = (mode & 0010) ? 'x' : '-';
    // 设置其他人的权限信息
    tm_str[7] = (mode & 0004) ? 'r' : '-';
    tm_str[8] = (mode & 0002) ? 'w' : '-';
    tm_str[9] = (mode & 0001) ? 'x' : '-';
    tm_str[10] = '\0';  // 字符串以null结尾
}

void set_time(time_t mtime, char *time_str)
{
    // 由于tm结构体中存储的是月份的整数值，我们需要的是月份字符串，所以用一个字符串数组来存储月份字符串
    const char month_arr[][10] = {
        "1月", "2月", "3月", "4月", "5月", "6月",
        "7月", "8月", "9月", "10月", "11月", "12月"
    };
    // 调用localtime函数，获取tm结构体指针
    struct tm *st_tm = localtime(&mtime);
// 构建时间字符串,格式为：月份 天数 时：分
    sprintf(time_str, "%s %2d %02d:%02d",
            month_arr[st_tm->tm_mon],
            st_tm->tm_mday,
            st_tm->tm_hour,
            st_tm->tm_min);
}

// 文件名的字典排序，排序目录项结构体指针数组
int compare(const void *a, const void *b)
{
     // 此时a和b都是指向目录项结构体指针的指针，所以它们是二级指针，需要强转再解引用
    struct dirent * dir_a = * (struct dirent **)a;
    struct dirent * dir_b = *(struct dirent **)b;
    // 将文件名都转换成小写字母在按字典排序
    // 原始文件名不能被修改，所以创建临时数组来存储文件名的副本
    char file_name[256];
    char file_name2[256];
    // 确保字符串有一位'\0'
    strncpy(file_name, dir_a->d_name, sizeof(file_name) - 1);
    strncpy(file_name2, dir_b->d_name, sizeof(file_name2) - 1);
    // 确保字符串以空字符终止
    file_name[sizeof(file_name)-1] = '\0';
    file_name2[sizeof(file_name2)-1] = '\0';
    
    // 将副本转换为小写
    str_to_lower(file_name);
    str_to_lower(file_name2);

    // 返回副本文件名的字典顺序 升序
    return strcmp(file_name, file_name2);
}

// 将传入的字符串都转换成小写字母
void str_to_lower(char *str)
{
    while(*str)
    {
        *str = tolower(*str);
        str++;
    }
}






